<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 1.选择排序/冒泡排序
        function fn1() {
            const arr = [3, 1, 4, 5, 9, 2, 6, 8, 7, 0];
            /* 选择排序 */
            let temp;
            for (let i = 0; i < arr.length - 1; i++) {
                let smallest = arr[i];
                let smallestIndex = i;
                for (let j = i; j < arr.length; j++) {
                    if (arr[j] < smallest) {
                        smallest = arr[j]
                        smallestIndex = j
                    }
                }
                temp = arr[i]
                arr[i] = smallest;
                arr[smallestIndex] = temp
            }
            console.log(arr);
        }
        // fn1()

        function fn2() {
            const arr = [3, 1, 4, 5, 9, 2, 6, 8, 7, 0];
            /* 冒泡排序 */
            for (let i = 0; i < arr.length - 1; i++) {
                for (let j = 0; j < arr.length - i - 1; j++) {
                    if (arr[j] > arr[j + 1]) {
                        let temp = arr[j];
                        arr[j] = arr[j + 1];
                        arr[j + 1] = temp
                    }
                }
            }
            console.log(arr);
        }
        // fn2()

        // 2.深拷贝
        function fn3() {
            // 手写深拷贝：deepCopy(obj)返回一个obj的深拷贝副本
            // {name:"jack",age:20,gender:"男",clazz:"2106",wife:{name:"rose",age:20,gender:"女",clazz:"2204"}}
            let obj = {
                name: "jack", age: 20, gender: "男", clazz: "2106",
                wife: { name: "rose", age: 20, gender: "女", clazz: "2204" }
            }
            function shenkaobei(newObj) {
                if (typeof newObj !== 'object' && typeof newObj !== 'function') {
                    return newObj;
                } else if (typeof newObj === 'function') {
                    return newObj;
                } else if (Array.isArray(newObj)) {
                    let arrcopy = [];
                    newObj.forEach(
                        function (time, index) {
                            arrcopy[index] = shenkaobei(item);
                        }
                    )
                    return arrcopy;
                } else {
                    let objCopy = {};
                    for (let key in newObj) {
                        let value = newObj[key];
                        objCopy[key] = shenkaobei(value)
                    }
                    return objCopy;
                }
            }
            deepCopy = shenkaobei(obj);
            console.log(deepCopy);
        }
        // fn3()

        // 3.手撸防抖于节流
        function fn4() {
            /* 防抖高阶函数 */
            function mydebounce(fn, delay) {
                let timer = null;
                return function (...args) {
                    if (timer) {
                        clearTimeout(timer);
                    }
                    timer = setTimeout(() => {
                        fn.apply(null, args);
                        timer = null;
                    }, delay)
                }
            }

            /* 节流 无定时器版本 */
            function mythrottle(fn, delay) {
                let lastCallTime = 0;
                return function (...args) {
                    if (Date.now() - lastCallTime >= delay) {
                        fn.apply(null, args);
                        lastCallTime = Date.now()
                    }
                }
            }

            /* 节流 标准timer版 */
            function mythrottle(fn, delay) {
                let timer = null;
                return function (...args) {
                    if (!timer) {
                        fn.apply(null, args);

                        timer = setTimeout(() => {
                            timer = null;
                        }, delay)
                    }
                }
            }
        }
        // fn4()

        // 4.使用连环异步回调实现求5的阶乘：
        function fn5() {
            function multiply(a, b, callback) {
                setTimeout(() => callback(a * b), 2000)
            }

            function mulPromise(a, b) {
                return new Promise((resolve) => {
                    multiply(a, b, (ret) => resolve(ret))
                })
            }

            /* 链式回调方法 */
            function chainDemo() {
                mulPromise(2, 3)
                    .then((ret) => {
                        console.log(ret);
                        return mulPromise(ret, 4)
                    })
                    .then((ret) => {
                        console.log(ret);
                        return mulPromise(ret, 5)
                    })
                    .then((ret) => {
                        console.log(ret);
                    })
            }
            // chainDemo()

            async function awaitDemo() {
                let ret = await mulPromise(2, 3)
                console.log(ret);

                ret = await mulPromise(ret, 4);
                console.log(ret);

                ret = await mulPromise(ret, 5)
                console.log(ret);
            }
            // awaitDemo()
        }
        // fn5()

        // 5.提取URL中的查询参数
        function fn6() {
            // 假设url为/add?a=12&b=34#abc，要求得到{a:12,b:34}
            function getSearchParams(url) {
                //实现之
                const obj = {};
                const reg = /\w+=\w+/g
                url.match(reg).forEach(
                    item => {
                        let [key, value] = item.split("=");
                        obj[key] = value
                    }
                )
                return obj;
            }
            console.log(getSearchParams("/add?a=12&b=34#abc"));
        }
        // fn6()

        // 6.封装ajax,实现POST一个表单 + promise版
        function fn7() {
            function objToParams(obj) {
                let str = "";
                for (let key in obj) {
                    str += `&${key}=${obj[key]}`;
                }
                return str.slice(1);
            }

            function ajax({ url, method, dataType, data, onSuccess, onFail }) {
                let reqBody = null;

                method = method || 'GET';
                onSuccess = onSuccess || ((data) => console.log(data));
                onFail = onFail || ((err) => console.log(err));

                const xhr = new XMLHttpRequest();
                xhr.open(method, url)

                switch (dataType) {
                    case 'form':
                        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                        reqBody = objToParams(data)
                        break;

                    case 'json':
                        xhr.setRequestHeader('Content-Type', 'application/json');
                        reqBody = JSON.stringify(data);
                        break;

                    default:
                        break;
                }

                xhr.onload = function () {
                    onSuccess(xhr.responseText)
                }
                xhr.onerror = function (err) {
                    onFail(err)
                }
                xhr.send(reqBody)
            }

            // ajax({
            //     url: "http://www.httpbin.org/post",
            //     method: "POST",
            //     dataType: "form",
            //     data: { name: "heige", pwd: "123456" },
            //     onSuccess: (data) => console.log("data=", data),
            //     onFail: (err) => console.log("err=", err),
            // });

            /* promise版 */
            function ajaxPromise(config) {
                return new Promise((resolve, reject) => {
                    ajax({
                        ...config,
                        onSuccess: (data) => resolve(data),
                        onFail: (err) => reject(err),
                    });
                });
            }

            /* 调用 */
            ajaxPromise({
                url: "http://www.httpbin.org/post",
                method: "POST",
                dataType: "form",
                data: { name: "heige", pwd: "123456" },
                // onSuccess: (data) => console.log("data=", data),
                // onFail: (err) => console.log("err=", err),
            })
                .then((data) => console.log("data=", data))
                .catch((err) => console.log("err=", err));
        }
        // fn7()

        // 7.带有超时功能的promise
        function fn8() {
            async function executeWithTimeout(fn, ms) {
                return new Promise((resolve, reject) => {
                    let timer = setTimeout(() => {
                        reject('timeout')
                    }, ms);

                    fn().then((ret) => {
                        clearTimeout(timer);
                        resolve(ret);
                    })
                });
            }

            function rq(url) {
                return new Promise((resolve) => {
                    setTimeout(() => {
                        resolve(`${url}的网页`)
                    }, 1000)
                })
            }

            async function fn() {
                return rq('https//taobao.com')
            }

            executeWithTimeout(fn, 3000)
                .then((ret) => console.log(ret))
                .catch((err) => console.log(err))

        }
        // fn8()

        // 8.一次性获取多个指定页面
        function fn9() {
            async function getContentByUrl(url) {
                return new Promise((resolve) => {
                    setTimeout(() => {
                        resolve(`${url}的网页内容`);
                    }, 1000);
                });
            }
            const urls = [
                "https://www.taobao.com",
                "https://www.baidu.com",
                "https://web.taobao.com",
            ]

            function fetchData(urls) {
                return Promise.allSettled(urls.filter((url) => url.endsWith('taobao.com')).map((url) => getContentByUrl(url)))
            }

            fetchData(urls).then((ret) => console.log(ret))
        }
        // fn9()

        // 9.ip地址比大小
        function fn10() {
            function compare(v1, v2) {
                var arr1 = v1.split(".");
                var arr2 = v2.split(".");

                arr1 = arr1.map((item) => item * 1);
                arr2 = arr2.map((item) => item * 1);

                for (var i = 0; i < arr1.length; i++) {
                    if (arr1[i] > arr2[i]) {
                        return 1;
                    }

                    if (arr1[i] < arr2[i]) {
                        return -1;
                    }
                }

                return 0;
            }

            console.log(compare("1.11.111.123", "1.2.3.4"));//1
            console.log(compare("1.2.3.1423", "1.11.111.123"));//-1
            console.log(compare("1.11.111.123", "1.11.111.123"));//0
        }
        // fn10()

        // 10.实现闭包管理全班学生成绩
        function fn11() {
            function score(name) {
                //实现闭包内容
                const obj = {};
                return {
                    set(key, value) {
                        obj[key] = value
                    },
                    getAll() {
                        return obj
                    }
                }
            }
            const stus = ['张三', '李四', '王五']
            const scoreObj = {}
            let name = '李四', key = '语文', value = 50;
            stus.forEach(
                name => scoreObj[name] = score(name)
            )

            scoreObj[name].set(key, value)
            console.log(JSON.stringify(scoreObj[name].getAll()));
        }
        // fn11()

        // 11.手封MyMap实现以下效果
        function fn12() {
            class MyMap {
                constructor() {
                    this.obj = {};
                    this.size = 0;
                }

                updataSize() {
                    this.size = Object.keys(this.obj).length
                }

                set(key, value) {
                    this.obj[key] = value;
                    this.updataSize()
                }

                forEach(handler) {
                    for (let key in this.obj) {
                        handler.apply(this, [this.obj[key], key, this])
                    }
                }

                entries() {
                    return Object.keys(this.obj).map(
                        (key) => ({
                            key, value: this.obj[key]
                        })
                    )
                }

                get(key) {
                    return this.obj[key]
                }

                has(key) {
                    return this.obj.hasOwnProperty(key);
                }

                delete(key) {
                    delete this.obj[key];
                    this.updataSize()
                }

                clear() {
                    this.obj = {};
                    this.updataSize();
                }
            }

            const map = new MyMap()
            map.set("name", "张三")
            map.set("age", 20)
            map.set("gender", "男")

            map.forEach(
                (value, key) => console.log(key, value)
            )//"name","张三" "age",20 "gender","男"

            for (let entry of map.entries()) {
                console.log(entry)
            }//{key:"name",value:"张三"} {key:"age",value:20} {key:"gender",value:"男"}

            console.log(map.size)//3
            console.log(map.get("name"))//张三
            console.log(map.get("age"))//20

            console.log(map.has("name"))//true
            console.log(map.has("myname"))//false

            map.delete("age")
            map.clear()
            console.log(map.size)//0
        }
        // fn12()

        // 12.全班人马排除空座位后抽取N名幸运观众
        function fn13() {
            let stus = [] //共100名学生
            for (let i = 1; i < 100; i++) {
                stus.push(i)
            }

            const empties = [12, 34, 56, 78]//空座位
            stus = stus.filter((s) => empties.indexOf(s) === -1)
            // let n = [1, 96]    //以内的随机整数

            function pick(arr, n) {
                //实现业务逻辑
                let luckies = [];

                // while(luckies.length < n) {
                //     let num = parseInt(Math.random() * arr.length)
                //     let stu = stus[num];

                //     !luckies.includes(stu) && luckies.push(stu)
                // }

                // 方法二
                for (let i = 0; i < n; i++) {
                    let randomIndex = parseInt(Math.random() * arr.length);
                    luckies = arr.splice(randomIndex, 1).concat(luckies)
                }
                return luckies;
            }

            console.log(pick(stus, 5))
        }
        // fn13()

        // 手撸观察者模式
        // 实现彩票机周期性地发布【开售】【开奖】事件，玩家开售时下注，开奖时购买，一旦中奖则彩票机停止工作；
        function fn14() {
            class Observable {
                constructor(name) {
                    this.name = name;
                    this.observers = [];
                }
                register(ob) {
                    this.observers.push(ob);
                    ob.observable = this;
                }
                unregister(ob) {
                    this.observers = this.observers.filter((o) => o !== ob);
                }
                emit(event) {
                    this.observers.forEach((ob) => ob.onEvent(event))
                }
            }
            class Observer {
                constructor(name) {
                    this.name = name
                }
                onEvent(event) {
                    console.log(this.name, "响应事件", event);
                }
            }
            class Lottery extends Observable {
                constructor(name) {
                    super(name);
                    this.timer = null;
                }
                start() {
                    if (!this.timer) {
                        this.timer = setInterval(() => {
                            const code = parseInt(Math.random() * 3);
                            console.log(this.name, "发布开奖事件", code);
                            this.emit({
                                type: "开奖",
                                code
                            });
                            this.emit({
                                type: "开售"
                            });
                        }, 2000);
                    }
                }
                stop() {
                    if (this.timer) {
                        clearInterval(this.timer);
                        console.log(this.name, "已停止");
                        this.timer = null;
                    }
                }
            }
            class Player extends Observer {
                constructor(name) {
                    super(name);
                    this.code = null;
                }
                buy() {
                    this.code = parseInt(Math.random() * 3);
                    console.log(this.name, "下注", this.code);
                }
                check(event) {
                    const isLucky = this.code === event.code;
                    console.log(this.name, isLucky ? "中奖了" : "未中奖");
                    isLucky && this.observable.stop();
                }
                onEvent(event) {
                    super.onEvent(event);
                    switch (event.type) {
                        case "开售":
                            this.buy();
                            break;
                        case "开奖":
                            this.check(event);
                            break;

                        default:
                            break;
                    }
                    console.log("");
                }
            }
            /* 业务逻辑 */
            ~(function main() {
                const lot = new Lottery("大乐透");
                const zhangsan = new Player("张三");
                const lisi = new Player("李四");

                lot.register(zhangsan);
                lot.register(lisi);
                lot.unregister(lisi);

                lot.start();
            })();
        }
        // fn14()

        // 实现任意多个入参的函数fn的柯里化
        function fn15() {
            const curry = (fn) => {
                //实现之
                return function cfn(...args) {
                    if (args.length === fn.length) {
                        return fn.apply(null, args)
                    }
                    return function (...b) {
                        args = args.concat(b);
                        return cfn(...args)
                    }
                }
            };

            // add函数进去 颗粒化了的cadd函数出来
            const add = (a, b, c, d) => a + b + c + d;
            const cadd = curry(add);

            // 一次性给够参数 = add(1,2,3,4)
            console.log(cadd(1, 2, 3, 4)); //10
            console.log(cadd(1)(2)(3)(4)); //10
            console.log(cadd(1, 2)(3)(4)); //10

        }
        // fn15()

        // 实现任意多个函数的管道与组合
        function fn16() {
            /* 管道 */
            // const pipe = (a,b,c)=>v=>c(b(a(v)))
            const pipe = (...fns) => (v => fns.reduce((pv, fn, index) => fn(pv), v))
            const compose = (...fns) => (v => fns.reverse().reduce((pv, fn, index) => fn(pv), v))

            //求数值的长度
            const len = (n) => (n + "").length;
            //求n的平方
            const pow = (n) => n * n;
            // 求立方根
            const cubicRoot = (n) => Math.cbrt(n);

            // 从左往右处理
            console.log(pipe(len, pow, cubicRoot)(10));//10的长度为2 平方后得4 开三方得1.x

            // 从右往左处理
            console.log(compose(len, pow, cubicRoot)(1000));//1000立方根10 平方后100 长度3
        }
        // fn16()

        // 函数的Promise化
        function fn17() {
            // 同步函数
            function add(a, b) {
                return a + b;
            }

            /* 同步函数的promise化 */
            function promisify1(fn) {
                return function (...args) {
                    return new Promise((resolve, reject) => {
                        const ret = fn(...args)
                        resolve(ret)
                    })
                }
            }

            /* 同步返回函数的Promise化 */
            const padd = promisify1(add)
            padd(2, 3).then(
                value => console.log("value=", value)//5
            )

            // 带有异步回调的函数
            function multiply(a, b, callback) {
                setTimeout(() => callback(a * b), 1000);
            }
            // multiply(2, 3, (ret) => console.log("ret=", ret));

            /* 异步函数的Promise化 */
            function promisify(fn) {
                //fn进去 Promise化的fn出来 实现之
                return function (...args) {
                    return new Promise((resolve, reject) => {
                        try {
                            const ret = fn(...args, (...a) => resolve(a))
                            ret && resolve(ret)
                        } catch (err) {
                            reject(err)
                        }
                    })
                }
            }

            /* 异步回调函数的Promise化 */
            const pmul = promisify(multiply);
            pmul(2, 3)
                .then((values) => {
                    console.log("value=", values);//6
                    return pmul(values[0], 4);
                })
                .then((values) => {
                    console.log("value=", values);//24
                    return pmul(values[0], 5);
                })
                .then((values) => console.log(values[0]));//120
        }
        // fn17()

        // 封装MongoDB数据引擎层
        async function fn18() {
            // var MongoClient = require("mongodb").MongoClient;
            // var url = "mongodb://localhost:27017/";
            // MongoClient.connect(url, function (err, db) {
            //     if (err) throw err;
            //     var dbo = db.db("runoob");
            //     var myobj = { name: "菜鸟教程", url: "www.runoob" };
            //     dbo.collection("site").insertOne(myobj, function (err, res) {
            //         if (err) throw err;
            //         console.log("文档插入成功");
            //         db.close();
            //     });
            // });

            /* 通用的获取collection连接 */
            function getCollection(dbName, collectionName) {
                //实现之
                return new Promise((resolve, reject) => {
                    var MongoClient = require("mongodb").MongoClient;
                    var url = "mongodb://localhost:27017/";

                    MongoClient.connect(url, function (err, db) {
                        if (err) {
                            resolve({
                                err,
                                collection: undefined,
                                db: undefined
                            });
                            return;
                        }

                        var dbo = db.db(dbName);
                        const collection = dbo.collection(collectionName);
                        resolve({
                            err: undefined,
                            collection,
                            db
                        })
                    })
                })
            }

            /* 通用插入数据 */
            async function doCreate(dbName, collectionName, dataObj) {
                //实现之
                const { err, collection, db } = await getCollection(dbName, collectionName)

                if (err) {
                    return {
                        err,
                        res: undefined
                    }
                }

                collection.insertOne(dataObj, function (err, res) {
                    db.close

                    // 将数据和插入的一并履约
                    return {
                        err,
                        res,
                    }
                })
            }

            // 模型层调用
            async function addUser(user) {
                // 对控制层返回的是一个Promise对象
                const ret = await doCreate(dbName, collectionName, user);
                return ret;
            }
        }
        // fn18()

        // 手撸数据驱动视图微框架
        function fn19() {
            function defineReactive(data, keys = []) {
                /* keys中有多少key 就定义多少个属性 */
                keys.forEach(
                    // obj=>{}
                    ({ name, listeners }) => {
                        /* 一个name=一个响应式数据属性 */
                        Object.defineProperty(data, name, {
                            // 读取data.age
                            get() {
                                console.log("get", name);
                                return data[`x_${name}`];
                            },

                            // 设置data.age = 19
                            set(newValue) {
                                console.log("set", name, newValue);
                                data[`x_${name}`] = newValue;

                                /* 通知age的观察者来响应 */
                                // hMyage.innerText = newValue;
                                // pMyage.innerText = newValue;
                                listeners.forEach((dom) => (dom.innerText = newValue));
                            },

                        });
                    }
                );
            }

            /* 业务逻辑代码 */
            const data = {};

            /* 提前定义响应式数据 */
            defineReactive(data, [
                { name: "age", listeners: [hMyage, pMyage] },
                { name: "name", listeners: [hMyname, pMyname] },
                { name: "gender", listeners: [pGender] },
            ]);

            /* 初始化响应式数据 */
            data.name = "黑哥";
            data.age = 18;
            data.gender = "男";

            /* 响应式数据动态变化 */
            setInterval(() => {
                data.name = Date.now();
                data.age = data.age + 1;
                data.gender = data.gender === "男" ? "女" : "男";
            }, 1000);
        }
        // fn19()

        // 数组的批处理API：forEach,filter,map,every,some,find,reduce
        function fn20() {
            /* 使用批处理函数实现如下功能 */
            const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
            // 遍历数组
            arr.forEach(item => console.log(item));

            //  过滤出3的倍数，打印过滤后的结果数组
            console.log(arr.filter(item => item % 3 === 0));

            // 将所有元素映射为对应的平方数，打印映射出的新数组
            console.log(arr.map(item => Math.pow(item, 2)));

            // 判断arr中是否所有元素都是3的倍数
            console.log(arr.every(item => item % 3 === 0));

            // 判断arr中是否有元素都是3的倍数
            console.log(arr.some(item => item % 3 === 0));

            // 从数组中找出第一个3的倍数
            console.log(arr.find(item => item % 3 === 0));

            // 求arr中所有元素的和，使用reduce实现
            console.log(arr.reduce((pv, cv) => pv + cv));

            // 在100的基础上，加入arr中所有元素的和，使用reduce实现
            console.log(arr.reduce((pv, cv) => pv + cv, 100));
        }
        // fn20()
    </script>
</body>

</html>